package com.pascuit.gpxhandler.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.google.gwt.ajaxloader.client.ArrayHelper;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.maps.client.MapOptions;
import com.google.gwt.maps.client.MapTypeId;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.overlays.Polyline;
import com.google.gwt.maps.client.overlays.PolylineOptions;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.visualization.client.AbstractDataTable;
import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.Selection;
import com.google.gwt.visualization.client.VisualizationUtils;
import com.google.gwt.visualization.client.events.SelectHandler;
import com.google.gwt.visualization.client.visualizations.PieChart;
import com.google.gwt.visualization.client.visualizations.PieChart.Options;
import com.pascuit.gpxhandler.shared.dto.GpxInfoDto;
import com.pascuit.gpxhandler.shared.dto.LatLngDto;
import com.pascuit.gpxhandler.shared.dto.UpdateInfoDto;
import com.pascuit.gpxhandler.shared.dto.UserDto;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.DateDisplayFormat;
import com.smartgwt.client.types.Encoding;
import com.smartgwt.client.types.FormMethod;
import com.smartgwt.client.types.TreeModelType;
import com.smartgwt.client.types.VisibilityMode;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.HTMLPane;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Slider;
import com.smartgwt.client.widgets.WidgetCanvas;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.events.ValueChangedEvent;
import com.smartgwt.client.widgets.events.ValueChangedHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.BooleanItem;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.UploadItem;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.Layout;
import com.smartgwt.client.widgets.layout.SectionStack;
import com.smartgwt.client.widgets.layout.SectionStackSection;
import com.smartgwt.client.widgets.tree.Tree;
import com.smartgwt.client.widgets.tree.TreeGrid;
import com.smartgwt.client.widgets.tree.TreeNode;

/**
 * 
 * @author Parant
 * 
 */
public class Gpxhandler implements EntryPoint {

	private BlobServiceAsync blobService = GWT.create(BlobService.class);
	private GpxServiceAsync gpxService = GWT.create(GpxService.class);
	private UserServiceAsync userService = GWT.create(UserService.class);
	private Panel infoPanel;
	private UpdateInfoDto updateInfo = new UpdateInfoDto();
	private HTMLPane generateButton;
	private FlexTable mainPanel;
	private CheckBox synchronizeButton = new CheckBox();
	private DynamicForm form;
	private DynamicForm gpxForm;
	private WaitPopup waitPopup = new WaitPopup();
	private HLayout infoLayout;
	private Long startTime;
	private Long endTime;
	private Canvas sliderCanvas;
	private TreeGrid userGrid;
	private MapWidget mapWidget;
	private ListGridRecord selectedRecord;

	public void onModuleLoad() {
		initComplete(this);

		userGrid = new TreeGrid();

		HLayout mainLayout = new HLayout();
		mainLayout.setHeight("96%");
		mainLayout.setWidth("96%");
		mainLayout.addMember(creatLeftSectionStack());
		final SectionStack rightSection = creatRightSectionStack();
		mainLayout.addMember(rightSection);
		RootPanel.get().add(mainLayout);
		loadTreeGrid();

		Runnable onLoadCallback = new Runnable() {
			public void run() {
				// Create a pie chart visualization.

				SectionStackSection googleChartIntegration = new SectionStackSection("Google chart integration");
				googleChartIntegration.setExpanded(true);
				googleChartIntegration.setItems(createCanvas(createGoogleChartPanel()));
				rightSection.addSection(googleChartIntegration);
			}
		};
		VisualizationUtils.loadVisualizationApi(onLoadCallback, PieChart.PACKAGE);
	}

	protected Canvas createCanvas(PieChart createGoogleChartPanel) {
		WidgetCanvas canvas = new WidgetCanvas(createGoogleChartPanel);
		return canvas;
	}

	private void loadTreeGrid() {
		userService.fetch(new AsyncCallback<List<UserDto>>() {

			@Override
			public void onFailure(Throwable caught) {
				com.google.gwt.user.client.Window.alert(caught.toString());
			}

			@Override
			public void onSuccess(List<UserDto> result) {
				Tree tree = new Tree();
				tree.setModelType(TreeModelType.CHILDREN);

				TreeNode[] treeNodes = new TreeNode[result.size()];
				int i = 0;
				for (UserDto userDto : result) {
					TreeNode treeNode = new TreeNode();
					treeNode.setTitle(userDto.getNickName());
					treeNode.setID(userDto.getKey());
					treeNodes[i] = treeNode;
					i++;
				}
				tree.setRoot(new TreeNode("root", treeNodes));
				userGrid.setData(tree);
			}
		});
	}

	private SectionStack creatRightSectionStack() {
		SectionStack rightSectionStack = new SectionStack();
		rightSectionStack.setWidth100();
		rightSectionStack.setShowResizeBar(true);
		rightSectionStack.setVisibilityMode(VisibilityMode.MULTIPLE);
		rightSectionStack.setAnimateSections(true);

		SectionStackSection mapSection = new SectionStackSection("Map");
		mapSection.setExpanded(true);
		mapSection.setItems(createMapPanel());

		SectionStackSection gpxInfo = new SectionStackSection("GPX info");
		gpxInfo.setResizeable(true);
		gpxInfo.setExpanded(true);
		gpxInfo.setItems(createInfoForm(new GpxInfoDto()));

		SectionStackSection gpxUpload = new SectionStackSection("GPX upload");
		gpxUpload.setExpanded(true);
		gpxUpload.setItems(createUploadForm());

		rightSectionStack.setSections(mapSection, gpxInfo, gpxUpload);
		return rightSectionStack;
	}

	private PieChart createGoogleChartPanel() {
		// HLayout layout = new HLayout();
		// layout.setAlign(Alignment.CENTER);
		// layout.setHeight("350px");
		// Create a pie chart visualization.
		PieChart pie = new PieChart(createTable(), createOptions());

		// pie.addSelectHandler(createSelectHandler(pie));
		// layout.addMember(pie);
		// layout.setVisible(true);
		// layout.draw();
		return pie;
	}

	private Options createOptions() {
		Options options = Options.create();
		options.setWidth(400);
		options.setHeight(240);
		options.set3D(true);
		options.setTitle("My Daily Activities");
		return options;
	}

	private AbstractDataTable createTable() {
		DataTable data = DataTable.create();
		data.addColumn(ColumnType.STRING, "Task");
		data.addColumn(ColumnType.NUMBER, "Hours per Day");
		data.addRows(2);
		data.setValue(0, 0, "Work");
		data.setValue(0, 1, 14);
		data.setValue(1, 0, "Sleep");
		data.setValue(1, 1, 10);
		return data;
	}

	private SelectHandler createSelectHandler(final PieChart chart) {
		return new SelectHandler() {
			@Override
			public void onSelect(SelectEvent event) {
				String message = "";

				// May be multiple selections.
				JsArray<Selection> selections = chart.getSelections();

				for (int i = 0; i < selections.length(); i++) {
					// add a new line for each selection
					message += i == 0 ? "" : "\n";

					Selection selection = selections.get(i);

					if (selection.isCell()) {
						// isCell() returns true if a cell has been selected.

						// getRow() returns the row number of the selected cell.
						int row = selection.getRow();
						// getColumn() returns the column number of the selected
						// cell.
						int column = selection.getColumn();
						message += "cell " + row + ":" + column + " selected";
					} else if (selection.isRow()) {
						// isRow() returns true if an entire row has been
						// selected.

						// getRow() returns the row number of the selected row.
						int row = selection.getRow();
						message += "row " + row + " selected";
					} else {
						// unreachable
						message += "Pie chart selections should be either row selections or cell selections.";
						message += "  Other visualizations support column selections as well.";
					}
				}

				SC.say("Info", message);
			}
		};
	}

	private Canvas createMapPanel() {
		HLayout layout = new HLayout();
		layout.setAlign(Alignment.CENTER);
		layout.setHeight("350px");

		LatLng center = LatLng.newInstance(47.167334854602814, 3.948090299963951);
		MapOptions opts = MapOptions.newInstance();
		opts.setZoom(14);
		opts.setCenter(center);
		opts.setMapTypeId(MapTypeId.TERRAIN);

		mapWidget = new MapWidget(opts);
		mapWidget.setSize("600px", "350px");

		layout.addMember(mapWidget);
		layout.setVisible(true);
		layout.draw();
		return layout;
	}

	private LatLng getPolyLineCenter(Polyline polyline) {
		if (polyline == null) {
			return null;
		}

		// checks
		JsArray<LatLng> path = polyline.getPath().getArray();
		if (path.length() == 0) {
			return null;
		}
		LatLng startPt = path.get(0);
		if (path.length() == 1) {
			return startPt;
		}

		double maxLat = startPt.getLatitude(), minLat = startPt.getLatitude(), maxLng = startPt.getLongitude(), minLng = startPt
				.getLongitude();
		for (int n = 1; n < path.length(); n++) {
			LatLng p = path.get(n);
			if (p.getLatitude() > maxLat) {
				maxLat = p.getLatitude();
			}
			if (p.getLongitude() > maxLng) {
				maxLng = p.getLongitude();
			}
			if (p.getLatitude() < minLat) {
				minLat = p.getLatitude();
			}
			if (p.getLongitude() < minLng) {
				minLng = p.getLongitude();
			}
		}

		return LatLng.newInstance(minLat + 0.5 * (maxLat - minLat), minLng + 0.5 * (maxLng - minLng));
	}

	private Canvas createSlider(final GpxInfoDto gpxInfo) {
		final long startMilli = gpxInfo.getStartDate().getTime();
		long endMilli = gpxInfo.getGpsEndDate().getTime();

		final long endSecond = endMilli / 1000;
		final long startSecond = startMilli / 1000;
		long totalTime = endSecond - startSecond;

		startTime = startSecond;
		endTime = endSecond;

		gpxForm.getField("targetStartDate").setValue(new Date(startMilli));
		gpxForm.getField("targetEndDate").setValue(new Date(endMilli));

		sliderCanvas = new Canvas();
		final Slider startSlider = new Slider("Start time (second)");
		startSlider.setVertical(false);
		startSlider.setMinValue(0);
		startSlider.setMaxValue(totalTime);
		startSlider.setNumValues(Integer.valueOf(String.valueOf(totalTime)));
		startSlider.setTop(30);
		startSlider.setLeft(15);
		startSlider.setWidth(450);
		sliderCanvas.addChild(startSlider);

		startSlider.addValueChangedHandler(new ValueChangedHandler() {
			public void onValueChanged(ValueChangedEvent event) {
				long value = event.getValue();

				long newValue = startSecond + value;
				Date date = new Date(newValue * 1000);
				gpxForm.getField("targetStartDate").setValue(date);
				startTime = newValue;
				upateLink(gpxInfo);
			}
		});

		final Slider endSlider = new Slider("End time (second)");
		endSlider.setVertical(false);
		endSlider.setMinValue(0);
		endSlider.setMaxValue(totalTime);
		endSlider.setNumValues(Integer.valueOf(String.valueOf(totalTime)));
		endSlider.setTop(90);
		endSlider.setLeft(15);
		endSlider.setWidth(450);
		endSlider.setValue(totalTime);
		sliderCanvas.addChild(endSlider);

		endSlider.addValueChangedHandler(new ValueChangedHandler() {
			public void onValueChanged(ValueChangedEvent event) {
				long value = event.getValue();

				long newValue = startSecond + value;
				Date endDate = new Date(newValue * 1000);
				endTime = newValue;
				gpxForm.getField("targetEndDate").setValue(endDate);
				upateLink(gpxInfo);
			}
		});
		sliderCanvas.addChild(createGenerateLink(gpxInfo));

		gpxForm.getField("synchronize").addChangedHandler(new ChangedHandler() {

			@Override
			public void onChanged(ChangedEvent event) {
				upateLink(gpxInfo);
			}
		});

		return sliderCanvas;
	}

	public static Window createWin(String title, boolean autoSizing, int width, int offsetLeft, Layout form) {
		Window window = new Window();
		window.setAutoSize(autoSizing);
		window.setTitle(title);
		window.setWidth(width);
		window.setLeft(offsetLeft);
		window.setCanDragReposition(true);
		window.setCanDragResize(true);
		window.addItem(form);
		return window;
	}

	public void updateForm(GpxInfoDto gpxInfo) {
		Date startDate = gpxInfo.getStartDate();
		Date endDate = gpxInfo.getGpsEndDate();

		gpxForm.getField("chronoStartDate").setValue(startDate);
		gpxForm.getField("gpsStartDate").setValue(gpxInfo.getGpsStartDate());
		gpxForm.getField("activityName").setValue(gpxInfo.getName());
		gpxForm.getField("gpsEndDate").setValue(endDate);

		infoLayout.addMember(createSlider(gpxInfo));

		gpxForm.redraw();
		infoLayout.redraw();
	}

	private Widget createGenerateLink(GpxInfoDto gpxInfo) {
		generateButton = new HTMLPane();
		generateButton.setLeft(10);
		generateButton.setHeight(50);
		generateButton.setContents(getUrl(gpxInfo));
		return generateButton;
	}

	private void upateLink(GpxInfoDto gpxInfo) {
		generateButton.setContents(getUrl(gpxInfo));
	}

	private String getUrl(GpxInfoDto gpxInfo) {
		Boolean synchro = (Boolean) gpxForm.getField("synchronize").getValue();

		return "<a href=\"/gpxhandler/gpxService?blob-key=" + gpxInfo.getBlogKey() + "&startTime=" + startTime
				+ "&endTime=" + endTime + "&synchronize=" + synchro + "\">Generate target<a/>";
	}

	private HLayout createUploadForm() {
		HLayout layout = new HLayout(20);
		form = new DynamicForm();
		form.setEncoding(Encoding.MULTIPART);
		form.setMethod(FormMethod.POST);

		UploadItem fileItem = new UploadItem("image");
		fileItem.setName("gpxUploadWidget");
		fileItem.setTitle("File");
		form.setTarget("downloadFrame");

		form.setFields(new FormItem[] { fileItem });
		layout.addMember(form);
		layout.addMember(createSubmitButton());

		return layout;
	}

	private SectionStack creatLeftSectionStack() {
		SectionStack leftSideLayout = new SectionStack();
		leftSideLayout.setWidth(280);
		leftSideLayout.setShowResizeBar(true);
		leftSideLayout.setVisibilityMode(VisibilityMode.MULTIPLE);
		leftSideLayout.setAnimateSections(true);

		SectionStackSection suppliesCategorySection = new SectionStackSection("Users");
		suppliesCategorySection.setExpanded(true);
		suppliesCategorySection.setItems(userGrid);

		leftSideLayout.setSections(suppliesCategorySection);
		return leftSideLayout;
	}

	private HLayout createInfoForm(GpxInfoDto gpxInfo) {
		infoLayout = new HLayout();

		gpxForm = new DynamicForm();
		gpxForm.setWidth(400);

		TextItem activityName = new TextItem();
		activityName.setTitle("Activity name");
		activityName.setName("activityName");
		activityName.setRequired(true);
		activityName.setDisabled(true);
		activityName.setValue(gpxInfo.getName());

		TextItem chronoStartDate = new TextItem();
		chronoStartDate.setTitle("Chrono start time");
		chronoStartDate.setName("chronoStartDate");
		chronoStartDate.setRequired(true);
		chronoStartDate.setDisabled(true);
		chronoStartDate.setValue(gpxInfo.getGpsStartDate());
		chronoStartDate.setDateFormatter(DateDisplayFormat.TOEUROPEANSHORTDATETIME);

		TextItem gpsStartDate = new TextItem();
		gpsStartDate.setTitle("GPS start time");
		gpsStartDate.setName("gpsStartDate");
		gpsStartDate.setRequired(true);
		gpsStartDate.setDisabled(true);
		gpsStartDate.setValue(gpxInfo.getGpsStartDate());
		gpsStartDate.setDateFormatter(DateDisplayFormat.TOEUROPEANSHORTDATETIME);

		TextItem gpsEndDate = new TextItem();
		gpsEndDate.setTitle("GPS end time");
		gpsEndDate.setName("gpsEndDate");
		gpsEndDate.setRequired(true);
		gpsEndDate.setDisabled(true);
		gpsEndDate.setValue(gpxInfo.getGpsStartDate());
		gpsEndDate.setDateFormatter(DateDisplayFormat.TOEUROPEANSHORTDATETIME);

		TextItem targetStartDate = new TextItem();
		targetStartDate.setTitle("Target start time");
		targetStartDate.setName("targetStartDate");
		targetStartDate.setRequired(true);
		targetStartDate.setDisabled(true);
		targetStartDate.setDateFormatter(DateDisplayFormat.TOEUROPEANSHORTDATETIME);

		TextItem targetEndDate = new TextItem();
		targetEndDate.setTitle("Target end time");
		targetEndDate.setName("targetEndDate");
		targetEndDate.setRequired(true);
		targetEndDate.setDisabled(true);
		targetEndDate.setDateFormatter(DateDisplayFormat.TOEUROPEANSHORTDATETIME);

		BooleanItem needSynchro = new BooleanItem();
		needSynchro.setTitle("Synchronize Chrono and GPS");
		needSynchro.setName("synchronize");
		needSynchro.setRequired(true);
		needSynchro.setDisabled(false);
		needSynchro.setValue(false);

		gpxForm.setFields(new FormItem[] { activityName, chronoStartDate, gpsStartDate, gpsEndDate, targetStartDate,
				targetEndDate, needSynchro });
		infoLayout.addMember(gpxForm);
		gpxForm.setMargin(10);

		infoLayout.draw();
		return infoLayout;
	}

	private IButton createSubmitButton() {
		IButton submitButton = new IButton("Upload");
		submitButton.setLeft(300);
		submitButton.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent event) {
				waitPopup.show();
				blobService.getBlobStoreUploadUrl(new AsyncCallback<String>() {

					@Override
					public void onSuccess(String result) {
						form.setAction(result.toString());
						form.submitForm();
					}

					@Override
					public void onFailure(Throwable caught) {
						waitPopup.hide();
						com.google.gwt.user.client.Window.alert(caught.toString());
					}
				});
			}
		});
		return submitButton;
	}

	public void uploadComplete(String status) {
		blobService.getGpxInfo(status, new AsyncCallback<GpxInfoDto>() {

			@Override
			public void onFailure(Throwable caught) {
				com.google.gwt.user.client.Window.alert(caught.toString());
				waitPopup.hide();
			}

			@Override
			public void onSuccess(final GpxInfoDto gpx) {
				UserDto user = new UserDto();
				gpx.setUserDto(user);
				user.setNickName("Kikival");

				gpxService.populateGpxInfo(gpx, new AsyncCallback<GpxInfoDto>() {

					@Override
					public void onFailure(Throwable caught) {
						com.google.gwt.user.client.Window.alert(caught.toString());
						waitPopup.hide();
					}

					@Override
					public void onSuccess(GpxInfoDto result) {
						waitPopup.hide();
						updateForm(result);
						updateMap(result);
					}
				});

			}
		});
	}

	private void updateMap(GpxInfoDto result) {
		ArrayList<LatLng> ptList = new ArrayList<LatLng>(result.getLatLng().size());
		for (LatLngDto latLng : result.getLatLng()) {
			ptList.add(LatLng.newInstance(latLng.getLat(), latLng.getLng()));
		}
		JsArray<LatLng> bigLatLngArr = ArrayHelper.toJsArray(ptList.toArray(new LatLng[] {}));
		PolylineOptions opts2 = PolylineOptions.newInstance();
		opts2.setMap(mapWidget);
		opts2.setPath(bigLatLngArr);
		opts2.setStrokeColor("#FF0000");
		opts2.setStrokeOpacity(1.0);
		opts2.setStrokeWeight(2);

		Polyline bigPolyLine = Polyline.newInstance(opts2);
		mapWidget.setCenter(getPolyLineCenter(bigPolyLine));
	}

	private static native void initComplete(Gpxhandler upload) /*-{
		$wnd.uploadComplete = function(fileName) {
			upload.@com.pascuit.gpxhandler.client.Gpxhandler::uploadComplete(Ljava/lang/String;)(fileName);
		};
	}-*/;
}
